﻿using System;
using System.Text.RegularExpressions;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Resolve;
using JetBrains.ReSharper.Psi.Tree;

namespace Neovolve.Extensibility.ReSharper
{
    /// <summary>
    /// The <see cref="NodeAnalyser"/>
    /// class is used to analyse <see cref="ITreeNode"/> instances to determine how they should be processed.
    /// </summary>
    internal static class NodeAnalyser
    {
        /// <summary>
        /// Stores the type mappings of alias type declarations.
        /// </summary>
        private static AliasTypeMappingCollection _aliasTypeMappings;

        /// <summary>
        /// Gets the format type suggestion.
        /// </summary>
        /// <param name="typeName">
        /// Name of the type.
        /// </param>
        /// <param name="option">
        /// The option.
        /// </param>
        /// <returns>
        /// A <see cref="TypeAliasFormat"/> instance.
        /// </returns>
        public static TypeAliasFormat GetFormatTypeSuggestion(String typeName, TypeAliasFormat option)
        {
            const String Expression = @"\[[0-9]*\]$";
            String baseTypeName = Regex.Replace(typeName, Expression, String.Empty);

            return CalculateFormatTypeSuggestion(option, baseTypeName);
        }

        /// <summary>
        /// Gets the format type suggestion.
        /// </summary>
        /// <param name="node">
        /// The node to check.
        /// </param>
        /// <param name="option">
        /// The option.
        /// </param>
        /// <returns>
        /// A <see cref="TypeAliasFormat"/> value.
        /// </returns>
        public static TypeAliasFormat GetFormatTypeSuggestion(ITreeNode node, TypeAliasFormat option)
        {
            // Check for type usage nodes
            if (node is IDeclaredTypeUsageNode || node is ITypeUsageNode || node is IReferenceExpressionNode
                || node is IPredefinedTypeExpressionNode || node is IDocCRefElementNode)
            {
                String typeName = node.GetText();
                TypeAliasFormat suggestion = GetFormatTypeSuggestion(typeName, option);

                if (suggestion == TypeAliasFormat.ConvertFromAlias)
                {
                    // Check that this node is not the base type of an enum declaration
                    if (node.Parent is IEnumBaseNode)
                    {
                        return TypeAliasFormat.Ignore;
                    }
                }

                return suggestion;
            }

            if (node is IDocCommentBlockNode)
            {
                // Get the cref items
                foreach (IReference reference in node.GetFirstClassReferences())
                {
                    if (reference is IDocCRefElementNode)
                    {
                        IDocCRefElementNode crefNode = reference as IDocCRefElementNode;
                        String typeName = crefNode.GetText();
                        TypeAliasFormat suggestion = GetFormatTypeSuggestion(typeName, option);

                        if (suggestion != TypeAliasFormat.Ignore)
                        {
                            return suggestion;
                        }
                    }
                }
            }

            return TypeAliasFormat.Ignore;
        }

        /// <summary>
        /// Gets the new name of the type.
        /// </summary>
        /// <param name="currentTypeName">
        /// Name of the current type.
        /// </param>
        /// <param name="suggestion">
        /// The suggestion.
        /// </param>
        /// <returns>
        /// A <see cref="string"/> value.
        /// </returns>
        public static String GetNewTypeName(String currentTypeName, TypeAliasFormat suggestion)
        {
            const String Expression = @"\[[0-9]*\]$";
            String baseTypeName = Regex.Replace(currentTypeName, Expression, String.Empty);
            String newTypeName = baseTypeName;
            AliasTypeMapping mapping = AliasTypeMappings.FindByTypeName(baseTypeName);

            if (suggestion == TypeAliasFormat.ConvertFromAlias)
            {
                newTypeName = mapping.ClrTypeName;
            }
            else if (suggestion == TypeAliasFormat.ConvertToAlias)
            {
                newTypeName = mapping.AliasTypeName;
            }

            // Check if there is an array specifier
            if (Regex.IsMatch(currentTypeName, Expression))
            {
                // Add the array specifier back into the type
                String suffix = currentTypeName.Substring(baseTypeName.Length);

                newTypeName += suffix;
            }

            // Return the new type
            return newTypeName;
        }

        /// <summary>
        /// Calculates the format type suggestion.
        /// </summary>
        /// <param name="option">
        /// The option.
        /// </param>
        /// <param name="typeName">
        /// Name of the type.
        /// </param>
        /// <returns>
        /// A <see cref="TypeAliasFormat"/> instance.
        /// </returns>
        private static TypeAliasFormat CalculateFormatTypeSuggestion(TypeAliasFormat option, String typeName)
        {
            // Get the mapping for this type
            AliasTypeMapping mapping = AliasTypeMappings.FindByTypeName(typeName);

            // Check if no mapping was found
            if (mapping == null)
            {
                return TypeAliasFormat.Ignore;
            }

            // Alias types are stored as keys in the alias type mapping dictionary
            TypeResolution resolution = GetTypeResolution(mapping, typeName);

            // Check if the type is an alias type
            if (option == TypeAliasFormat.ConvertFromAlias
                && resolution == TypeResolution.AliasType)
            {
                return TypeAliasFormat.ConvertFromAlias;
            }

            // Full types are stored as values in the alias type mapping dictionary

            // Check if the type is a full type
            if (option == TypeAliasFormat.ConvertToAlias
                && resolution == TypeResolution.CLRType)
            {
                return TypeAliasFormat.ConvertToAlias;
            }

            return TypeAliasFormat.Ignore;
        }

        /// <summary>
        /// Generates the mappings.
        /// </summary>
        /// <returns>
        /// A <see cref="AliasTypeMappingCollection"/> instance.
        /// </returns>
        private static AliasTypeMappingCollection GenerateMappings()
        {
            AliasTypeMappingCollection mappings = new AliasTypeMappingCollection();

            mappings.Add(new AliasTypeMapping("object", "Object"));
            mappings.Add(new AliasTypeMapping("char", "Char"));
            mappings.Add(new AliasTypeMapping("string", "String"));
            mappings.Add(new AliasTypeMapping("bool", "Boolean"));
            mappings.Add(new AliasTypeMapping("byte", "Byte"));
            mappings.Add(new AliasTypeMapping("short", "Int16"));
            mappings.Add(new AliasTypeMapping("int", "Int32"));
            mappings.Add(new AliasTypeMapping("long", "Int64"));
            mappings.Add(new AliasTypeMapping("ushort", "UInt16"));
            mappings.Add(new AliasTypeMapping("uint", "UInt32"));
            mappings.Add(new AliasTypeMapping("ulong", "UInt64"));
            mappings.Add(new AliasTypeMapping("double", "Double"));
            mappings.Add(new AliasTypeMapping("decimal", "Decimal"));
            mappings.Add(new AliasTypeMapping("float", "Single"));
            mappings.Add(new AliasTypeMapping("sbyte", "SByte"));

            return mappings;
        }

        /// <summary>
        /// Gets the type resolution.
        /// </summary>
        /// <param name="mapping">
        /// The mapping.
        /// </param>
        /// <param name="typeName">
        /// Name of the type.
        /// </param>
        /// <returns>
        /// A <see cref="TypeResolution"/> instance.
        /// </returns>
        private static TypeResolution GetTypeResolution(AliasTypeMapping mapping, String typeName)
        {
            // Check if we have found that the type is a CLR type
            if (mapping.ClrTypeName == typeName)
            {
                return TypeResolution.CLRType;
            }

            // Check if we have found that the type is an alias type
            if (mapping.AliasTypeName == typeName)
            {
                return TypeResolution.AliasType;
            }

            // The type wasn't valid for type mapping
            return TypeResolution.None;
        }

        /// <summary>
        /// Gets the alias type mappings.
        /// </summary>
        /// <value>
        /// The alias type mappings.
        /// </value>
        public static AliasTypeMappingCollection AliasTypeMappings
        {
            get
            {
                // Check if the type mappings have been created
                if (_aliasTypeMappings == null)
                {
                    _aliasTypeMappings = GenerateMappings();
                }

                return _aliasTypeMappings;
            }
        }
    }
}